package pl.edu.mimuw.mas.editor.widget;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;

import pl.edu.mimuw.mas.bezier.ControlPoints;
import pl.edu.mimuw.mas.chart.Area;
import pl.edu.mimuw.mas.exception.WrongAreaException;

/**
 * Implementacja płótna do rysowania podglądu siatki
 * punktół kontrolnych.
 */
public class ControlPointsCanvas extends Canvas {
	
	// kolory - kompatybilne ze schematem TANGO
	private static final int[] BG_COLOUR = { 0x2e, 0x34, 0x36 };

	private static final int[] CONTROL_POINT_COLOUR = { 0xef, 0x29, 0x29 }; // czerwony
	private static final int[] GRID_COLOUR = { 0xfc, 0xe9, 0x4f };          // zolty
	private static final int[] SELECTION_COLOUR = { 0x88, 0x8a, 0x85 };     // szary jasny
	
	private static final int R = 0, G = 1, B = 2;
	
	private static final int MARGIN_TOP = 30;
	private static final int MARGIN_BOTTOM = 30;
	private static final int MARGIN_LEFT = 30;
	private static final int MARGIN_RIGHT = 30;
	
	private ControlPoints cp;
	
	private int mouseX0, mouseY0, mouseX1, mouseY1;
	private boolean moving = false;
	private boolean ctrl;
	
	private int realMarginLeft, realMarginTop;
	private int chartWidth, chartHeight;
	private int rows, columns;
	private double size;
	
	public ControlPointsCanvas(Composite c, int arg) {
		super(c, arg);
		
		cp = null;
		
		this.addPaintListener(new PaintListener() {
			@Override
			public void paintControl(PaintEvent e) {
				if (cp != null) {
					drawControlPoints(e);
				}
			}
		});
		
		this.addMouseListener(new MouseListener() {
			@Override
			public void mouseDoubleClick(MouseEvent e) {	}
			
			@Override
			public void mouseDown(MouseEvent e) {
				moving = true;
				mouseX0 = e.x;
				mouseY0 = e.y;
			}
			
			@Override
			public void mouseUp(MouseEvent e) {
				moving = false;
				mouseX1 = e.x;
				mouseY1 = e.y;
				updateSelection(true);
			}
		});
		
		this.addKeyListener(new KeyListener() {
			@Override
			public void keyPressed(KeyEvent e) {
				if (e.keyCode == SWT.CTRL && !moving) {
					ctrl = true;
				}
			}
			
			@Override
			public void keyReleased(KeyEvent e) {
				if (e.keyCode == SWT.CTRL) {
					ctrl = false;
				}
			}
		});
		
		this.addMouseMoveListener(new MouseMoveListener() {
			@Override
			public void mouseMove(MouseEvent e) {
				if (moving) {
					mouseX1 = e.x;
					mouseY1 = e.y;
					updateSelection(false);
				}
			}
		});
	}
	
	public void setControlPoints(ControlPoints cp) {
		this.cp = cp;
	}
	
	private void updateDrawingData() {
		Rectangle clientArea = this.getBounds();
		// Now web representing chart
		rows = (int) cp.getRowsCount();
		columns = (int) cp.getColumnsCount();
		double columnMaxSize = ((double) clientArea.width - MARGIN_LEFT - MARGIN_RIGHT - 1) / (columns - 1);
		double rowMaxSize = ((double) clientArea.height - MARGIN_BOTTOM - MARGIN_TOP - 1) / (rows - 1);
		size = Math.min(rowMaxSize, columnMaxSize);
		chartWidth = (int) (size * (columns - 1));
		chartHeight = (int) (size * (rows - 1));
		realMarginTop = MARGIN_TOP + (clientArea.height - MARGIN_TOP
				- MARGIN_BOTTOM - chartHeight) / 2;
		realMarginLeft = MARGIN_LEFT + (clientArea.width - MARGIN_LEFT
				- MARGIN_RIGHT 
				- chartWidth) / 2;
	}
	
	private void drawControlPoints(PaintEvent e) {
		updateDrawingData();
		Rectangle clientArea = this.getBounds();
		Color bg = new Color(Display.getCurrent(),
                             new RGB(BG_COLOUR[R], BG_COLOUR[G], BG_COLOUR[B]));
		e.gc.setBackground(bg);
		e.gc.setForeground(bg);
		e.gc.fillRectangle(0, 0, clientArea.width, clientArea.height);
		e.gc.setForeground(new Color(Display.getCurrent(),
                                     new RGB(GRID_COLOUR[R],
                                    		 GRID_COLOUR[G],
                                    		 GRID_COLOUR[B])));
		// draw column lines
		for (int i = 0; i < columns; i++) {
				e.gc.drawLine(realMarginLeft + (int) (i * size),
						realMarginTop,
						realMarginLeft + (int) (i * size),
						realMarginTop + chartHeight);
		}
		// draw row lines
		for (int i = 0; i < rows; i++) {
			e.gc.drawLine(realMarginLeft,
					realMarginTop + (int) (i * size),
					realMarginLeft + chartWidth,
					realMarginTop + (int) (i * size));
		}
		// draw selected points
		Color active = new Color(Display.getCurrent(),
                                 new RGB(CONTROL_POINT_COLOUR[R],
                                		 CONTROL_POINT_COLOUR[G],
                                		 CONTROL_POINT_COLOUR[B]));
		e.gc.setBackground(active);
		double pointSize;
		if (size > 20.) {
			pointSize = size / 4;
		} else if (size > 10.) {
			pointSize = size / 1.5;
		} else {
			pointSize = size;
		}
		for (int i = 0; i < columns; i++) {
			for (int j = 0; j < rows; j++) {
				if (cp.isSelected(i, rows - j - 1)) {
					e.gc.fillOval(realMarginLeft + (int) (i * size - pointSize / 2),
							realMarginTop + (int) (j * size - pointSize / 2),
							Math.max(1, (int) (pointSize)),
							Math.max(1, (int) (pointSize)));
				}
			}
		}
		// if we area moving mouse we need to color area
		if (moving) {
			Color gray = new Color(Display.getCurrent(),
                                   new RGB(SELECTION_COLOUR[R],
                                		   SELECTION_COLOUR[G],
                                		   SELECTION_COLOUR[B]));
			e.gc.setBackground(gray);
			e.gc.setAlpha(125);
			e.gc.fillRectangle(Math.min(mouseX0, mouseX1), Math.min(mouseY0, mouseY1),
					Math.abs(mouseX0 - mouseX1), Math.abs(mouseY0 - mouseY1));
		}
	}
	
	private void updateSelection(boolean propagate) {
		int x0, y0, x1, y1;
		// if selected area is out of chart then return
		if ((mouseX0 < realMarginLeft && mouseX1 < realMarginLeft) 
				|| (mouseX0 > realMarginLeft + chartWidth && mouseX1 > realMarginLeft + chartWidth) 
				|| (mouseY0 < realMarginTop && mouseY1 < realMarginTop) 
				|| (mouseY0 > realMarginTop + chartHeight && mouseY1 > realMarginTop + chartHeight)) {
			redraw();
			return;
		}
		
		if (Math.abs(mouseX0 - mouseX1) > size) {
			x0 = (int) Math.ceil((Math.min(mouseX0, mouseX1) - realMarginLeft) / size);
			y0 = rows - 1 - (int) Math.floor((Math.max(mouseY0, mouseY1) - realMarginTop) / size);
			x1 = (int) Math.floor((Math.max(mouseX0, mouseX1) - realMarginLeft) / size);
			y1 = rows - 1 - (int) Math.ceil((Math.min(mouseY0, mouseY1) - realMarginTop) / size);
		} else {
			x0 = (int) Math.round((Math.min(mouseX0, mouseX1) - realMarginLeft) / size);
			y0 = rows - 1 - (int) Math.round((Math.max(mouseY0, mouseY1) - realMarginTop) / size);
			x1 = (int) Math.round((Math.max(mouseX0, mouseX1) - realMarginLeft) / size);
			y1 = rows - 1 - (int) Math.round((Math.min(mouseY0, mouseY1) - realMarginTop) / size);
		}
		x0 = Math.min(Math.max(0, x0), columns - 1);
		y0 = Math.min(Math.max(0, y0), rows - 1);
		x1 = Math.min(Math.max(0, x1), columns - 1);
		y1 = Math.min(Math.max(0, y1), rows - 1);
	
		if (x0 > x1) { // which means there is only one column selected
			x1 = x0;
		}
		if (y0 > y1) { // which means there is only one row selected
			y1 = y0;
		}

		try {
			Area a = new Area(x0, y0, x1, y1);
			redraw();
			if (propagate) {
				Event e = new Event();
				e.data = a;
				int eventType = SWT.Modify;
				if (!ctrl) {
					eventType |= SWT.DefaultSelection;
				}
				notifyListeners(eventType, e);	
			}
		} catch (WrongAreaException e) {
			e.printStackTrace();
			// Area is not correct so we do nothing
		}
	}
	
}
